﻿using System;
using System.Collections.Generic;
using System.Linq;
using _5E.Infrastructure.Querying;
using NHibernate;
using NHibernate.Criterion;

namespace _5E.NHibernateRepository.Repository
{
    public static class QueryTranslator
    {
        public static ICriteria TranslateToNHibernateQuery<T>(this Query query, ICriteria criteria)
        {
            BuildQueryFrom(query, criteria);

            if (0 < query.Orders.Count())
            {
                foreach (OrderByClause order in query.Orders)
                {
                    criteria.AddOrder(new Order(order.PropertyName, order.Asc));
                }
            }

            return criteria;
        }

        private static void BuildQueryFrom(Query query, ICriteria criteria)
        {
            if (null != query.Criterions)
            {
                IList<ICriterion> criterions = CreateCriterionsBy(query);

                BuildQueryByQueryOperatorFrom(query, criterions, criteria);

                foreach (Query subQuery in query.SubQueries)
                {
                    BuildQueryFrom(subQuery, criteria);
                }
            }
        }

        private static IList<ICriterion> CreateCriterionsBy(Query query)
        {
            IList<ICriterion> criterions = new List<ICriterion>();
            foreach (Criterion c in query.Criterions)
            {
                ICriterion criterion;

                switch (c.CriteriaOperator)
                {
                    case CriteriaOperator.Eq:
                        criterion = Expression.Eq(c.PropertyName, c.Value);
                        break;
                    case CriteriaOperator.Le:
                        criterion = Expression.Le(c.PropertyName, c.Value);
                        break;
                    case CriteriaOperator.Lt:
                        criterion = Expression.Lt(c.PropertyName, c.Value);
                        break;
                    case CriteriaOperator.Ge:
                        criterion = Expression.Ge(c.PropertyName, c.Value);
                        break;
                    case CriteriaOperator.Gt:
                        criterion = Expression.Gt(c.PropertyName, c.Value);
                        break;
                    case CriteriaOperator.IsNull:
                        criterion = Expression.IsNull(c.PropertyName);
                        break;
                    case CriteriaOperator.NotNull:
                        criterion = Expression.IsNotNull(c.PropertyName);
                        break;
                    case CriteriaOperator.LikeStartWith:
                        criterion = Expression.Like(c.PropertyName, c.Value.ToString(), MatchMode.Start);
                        break;
                    case CriteriaOperator.LikeStartWithIgnoreCase:
                        criterion = Expression.Like(c.PropertyName, c.Value.ToString(), MatchMode.Start).IgnoreCase();
                        break;
                    case CriteriaOperator.LikeAnywhere:
                        criterion = Expression.Like(c.PropertyName, c.Value.ToString(), MatchMode.Anywhere);
                        break;
                    case CriteriaOperator.LikeAnywhereIgnoreCase:
                        criterion = Expression.Like(c.PropertyName, c.Value.ToString(), MatchMode.Anywhere).IgnoreCase();
                        break;
                    case CriteriaOperator.LikeEndWith:
                        criterion = Expression.Like(c.PropertyName, c.Value.ToString(), MatchMode.End);
                        break;
                    case CriteriaOperator.LikeEndWithIgnoreCase:
                        criterion = Expression.Like(c.PropertyName, c.Value.ToString(), MatchMode.End).IgnoreCase();
                        break;
                    default:
                        throw new ApplicationException("No operator defined!");
                }

                criterions.Add(criterion);
            }
            return criterions;
        }

        private static void BuildQueryByQueryOperatorFrom(Query query, IList<ICriterion> criterions, ICriteria criteria)
        {
            if (QueryOperator.And == query.QueryOperator)
            {
                Conjunction andSubQuery = Expression.Conjunction();
                foreach (ICriterion criterion in criterions)
                {
                    andSubQuery.Add(criterion);
                }

                criteria.Add(andSubQuery);
            }
            else
            {
                Disjunction orSubQuery = Expression.Disjunction();
                foreach (ICriterion criterion in criterions)
                {
                    orSubQuery.Add(criterion);
                }

                criteria.Add(orSubQuery);
            }
        }
    }
}
